package shared;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;

import org.w3c.dom.Node;

public class XMLRMIObjectTools {
	
	private static String className = "TempClass";
	private static String structName = "TempStruct";
	private static int classNum = 0; 
	private static int structNum = 0;

	public static Object createObject(Node valueNode, Class<?> typeAttendu) throws XMLRMIException, NotFoundException{
		
		System.out.println("Creation d'un objet pour le passage en parametre");
		
		//TODO Enlever ca puisque deja fait
		if( ! XMLRMIServicesTools.compare(valueNode, typeAttendu))
			throw new XMLRMIException("This node value doesn't match this type : "+typeAttendu.getSimpleName());
		
		if(XMLRMIDocumentTools.isASimpleTypeNodeValue(valueNode)){
			return createSimpleObject(valueNode, typeAttendu);
		}
		else if(XMLRMIDocumentTools.isAnObjectTypeNodeValue(valueNode)){
			return createNewObject(valueNode, typeAttendu);
		}
		else if(XMLRMIDocumentTools.isAValueNodeAnArray(valueNode)){
			return createArrayObject(valueNode, typeAttendu);
		}
		else if(XMLRMIDocumentTools.isAStructTypeNodeValue(valueNode)){
			return createNewStruct(valueNode, typeAttendu);
		}
		
		throw new XMLRMIException("The node has an unknown type !");
	}
	
	public static Object createSimpleObject(Node valueNode, Class<?> type) throws XMLRMIException{
		
		System.out.println("C'est un objet simple");
		
		String typeName = type.getSimpleName();
		
		if(typeName.equals("Integer")){
			try{
				return new Integer(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			}catch(NumberFormatException e){
				throw new XMLRMIException("An integer node value, is incorrect : " + e.getMessage());
			}
		}
		else if(typeName.equals("int")){
			int ret = Integer.parseInt(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			return ret;
		}
		else if(typeName.equals("Double")){
			try{
				return new Double(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			}catch(NumberFormatException e){
				throw new XMLRMIException("A double node value, is incorrect : " + e.getMessage());
			}
		}
		else if(typeName.equals("double")){
			double ret = Double.parseDouble(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			return ret;
		}
		else if(typeName.equals("Float")){
			try{
				return new Float(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			}catch(NumberFormatException e){
				throw new XMLRMIException("A float node value, is incorrect : " + e.getMessage());
			}
		}
		else if(typeName.equals("float")){
			float ret = Float.parseFloat(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			return ret;
		}
		else if(typeName.equals("Boolean")){
			try{
				return new Boolean(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			}catch(NumberFormatException e){
				throw new XMLRMIException("A boolean node value, is incorrect : " + e.getMessage());
			}
		}
		else if(typeName.equals("boolean")){
			boolean ret = Boolean.parseBoolean(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			return ret;
		}
		else if(typeName.equals("String")){
			try{
				return new String(XMLRMIDocumentTools.getValueOfValueNode(valueNode));
			}catch(NumberFormatException e){
				throw new XMLRMIException("A String node value, is incorrect : " + e.getMessage());
			}
		}
		//TODO DATETIME & BASE64
		
		
		throw new XMLRMIException("The type needed is not a simple type");
	}
	
	public static Object createNewObject(Node valueNode, Class<?> type)throws XMLRMIException, NotFoundException{
		
		System.out.println("C'est un nouvel objet");
		
		//Type is an interface because, we check it before
		if(!type.isInterface())
			throw new XMLRMIException("Type must be an interface to create a Class");
		
		//We get the object node
		Node objectNode = XMLRMIDocumentTools.getObjectNodeFromValue(valueNode);
		
		//We create a new class
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.makeClass(className+getNumClass());
		
		//We get the CtClass of the type
		CtClass ccType = pool.getCtClass(type.getName());
		
		//TODO Trouver une technique pour l'oid
		
		//A constructor to set the value of the fields
		CtConstructor constructor = new CtConstructor(null, cc);
		String srcConstructor = "";
		
		//We create the field
		Node fieldsNode = XMLRMIDocumentTools.getFieldsNodeFromObject(objectNode);
		for(int i=0; i<XMLRMIDocumentTools.getNbFieldFromFields(fieldsNode);i++){
			Node fieldNode = XMLRMIDocumentTools.getFieldIFromFields(fieldsNode, i);
			srcConstructor+=addField(cc, fieldNode);
		}
		
		//We add the oid field
		try {
			String oidString = XMLRMIDocumentTools.getOidFromObject(objectNode);
			CtField oidField = CtField.make("public String ___oid = "+ oidString +";", cc);
			System.out.println("Add the oid field with the value : "+oidString);
		} catch (CannotCompileException e1) {
			throw new XMLRMIException("Impossible to create a field for the oid !");
		}
		
		
		//We create the methods
		//We create the field
		Node methodsNode = XMLRMIDocumentTools.getMethodsNodeFromObject(objectNode);
		for(int i=0; i<XMLRMIDocumentTools.getNbMethodFromMethods(methodsNode);i++){
			Node methodNode = XMLRMIDocumentTools.getMethodIFromMethods(methodsNode,i);
			addMethod(cc, methodNode);
		}
		
		try {
			constructor.setBody("{"+srcConstructor+"}");
			cc.addConstructor(constructor);
		} catch (CannotCompileException e) {
			throw new XMLRMIException("Cannot Compile Constructor :"+ e.getMessage());
		}
		
		
		cc.addInterface(ccType);
		
		//TODO Les catchs
		Object o = null;
		try {
			//o = cc.toClass().newInstance();
			System.out.println("On cast le retour : "+ccType.getName());
			o = type.cast(cc.toClass().newInstance());
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (CannotCompileException e) {
			e.printStackTrace();
		}
		
		return o;
	}
	
	public static Object createNewStruct(Node valueNode, Class<?> type) throws XMLRMIException{
		//TODO
		return null;
	}
	
	//TODO Ajouter une interface commune entre les enfants
	public static Object createArrayObject(Node valueNode, Class<?> type) throws XMLRMIException{
		
		System.out.println("C'est un tableau");
		
		//TODO
		//Array node is not empty, so i can get the first element
		int tabSize = XMLRMIDocumentTools.getNbElementArrayValueNode(valueNode);
		
		Object o = Array.newInstance(type.getComponentType(), tabSize);
		
		for(int i=0; i<tabSize; i++){
			try {
				Array.set(o, i, createObject(XMLRMIDocumentTools.getINodeFromArrayValueNode(valueNode, i), type.getComponentType()));
			} catch (ArrayIndexOutOfBoundsException e) {
				throw new XMLRMIException("Array size error in array object :"+ e.getMessage());
			} catch (IllegalArgumentException e) {
				throw new XMLRMIException("Array argumentError in array object :"+ e.getMessage());
			} catch (NotFoundException e) {
				throw new XMLRMIException("Array NotFoundError in array object :"+ e.getMessage());
			}
		}
		
		
		return o;
	}
	
	public static synchronized int getNumClass(){
		classNum++;
		return classNum;
	}
	
	public static synchronized int getNumStruct(){
		structNum++;
		return structNum;
	}
	
	
	public static String addField(CtClass cc, Node fieldNode) throws XMLRMIException{
		System.out.println("Add a new Field : ");
		System.out.println(XMLRMIDocumentTools.getCorpsField(fieldNode));
		System.out.println(XMLRMIDocumentTools.getDeclarationField(fieldNode));
		
		try {
			CtField cf = CtField.make(XMLRMIDocumentTools.getCorpsField(fieldNode), cc);
			cc.addField(cf);
			return XMLRMIDocumentTools.getDeclarationField(fieldNode);
			
		} catch (CannotCompileException e) {
			throw new XMLRMIException("Cannot Compile Field :"+ e.getMessage());
		}	
	}
	
	public static void addMethod(CtClass cc, Node methodNode) throws XMLRMIException{
		System.out.println("Add a new Method : ");
		System.out.println(XMLRMIDocumentTools.getCorpsMethod(methodNode));
		
		try {
			CtMethod cm = CtMethod.make(XMLRMIDocumentTools.getCorpsMethod(methodNode), cc);
			cc.addMethod(cm);
		} catch (CannotCompileException e) {
			throw new XMLRMIException("Cannot Compile Method :"+ e.getMessage());
		}
		
	}
	
}
